package core

import (
	"errors"
	"fmt"
	"reflect"
	"strings"
	"time"

	"gitee.com/knowgo/ego/egoc"
	"gitee.com/knowgo/ego/log"

	"gitee.com/knowgo/ego/core/data"
	"gitee.com/knowgo/ego/core/errs"
	"gitee.com/knowgo/ego/core/function"
	"gitee.com/knowgo/ego/core/function/input"
	"gitee.com/knowgo/ego/core/inject"
	"gitee.com/knowgo/ego/core/types"
)

type Core struct {
	injectors *inject.InjectorManager
	groups    []*function.GroupInfo

	services map[string]*function.Service

	loaders map[reflect.Type]*function.ServiceLoader
	// ReponseObjects 返回值的类型池
	ReponseTypes map[string]*function.ResponseTypeInfo
	// ArgumentTypes 参数类型池
	ArgumentTypes map[string]*function.ArgumentTypeInfo

	TimeFormat egoc.TimeFormat

	// 日志
	log log.Logger
}

// NewCore 创建 Core
func NewCore(log log.Logger) *Core {
	return &Core{
		loaders:       map[reflect.Type]*function.ServiceLoader{},
		injectors:     inject.NewManager(),
		services:      map[string]*function.Service{},
		ReponseTypes:  map[string]*function.ResponseTypeInfo{},
		ArgumentTypes: map[string]*function.ArgumentTypeInfo{},
		log:           log,
		TimeFormat: egoc.TimeFormat{
			Datetime: "2006-01-02 15:04:05",
			Date:     "2006-01-02",
			Time:     "15:04:05",
		},
	}
}

func (c *Core) SetRequestTimeFormat(format egoc.TimeFormat) {
	if format.Datetime != "" {
		c.TimeFormat.Datetime = format.Datetime
	}
	if format.Date != "" {
		c.TimeFormat.Date = format.Date
	}
	if format.Time != "" {
		c.TimeFormat.Time = format.Time
	}
	if format.Location != nil {
		c.TimeFormat.Location = format.Location
	}
}

// RegisterInjector 注册注入类型
func (c *Core) RegisterInjector(fn interface{}) error {
	return c.injectors.Register(fn)
}

// Register 注册服务
func (c *Core) Register(group egoc.Loader) error {
	var groupDesc = group.Group()
	grpName := strings.TrimSpace(groupDesc.Name)
	if grpName == "" {
		return errors.New("group's Name is required")
	}
	grp := c.findGroup(grpName, groupDesc.Deprecated)
	if grp.Description == "" {
		grp.Description = groupDesc.Description
	}
	// grp.Deprecated =
	groupType := reflect.TypeOf(group)
	var infoMap = map[string]*egoc.FuncInfo{}
	// 解析函数 info 信息
	count := groupType.NumMethod()
	for n := 0; n < count; n++ {
		method := groupType.Method(n)
		if method.Name == "Group" && !strings.HasSuffix(method.Name, "Info") {
			// 跳过 Group 和非 Info 函数
			continue
		}
		if method.IsExported() {
			info, svcName := tryToParseFuncInfo(group, groupDesc, groupType, method)
			if info != nil {
				infoMap[svcName] = info
			}
		}
	}
	// 解析 functions

	// count := groupType.NumMethod()
	for n := 0; n < count; n++ {
		method := groupType.Method(n)
		if method.Name == "Group" {
			// 跳过 Group 函数
			continue
		}
		if method.IsExported() {
			svcName, err := grp.CheckServiceName(method.Name)
			if err != nil {
				// 同名 service 已经存在
				c.log.Warnf("ingore func %s.%s:\n%s", groupType.Name(), method.Name, err.Error())
				continue
			}

			if _, ok := infoMap[svcName]; ok {
				// 是 Info 函数
				continue
			}

			info := infoMap[fmt.Sprintf("%sInfo", svcName)]

			// 解析描述函数
			// info := tryToParseFuncInfo(group, groupType, method.Name)

			svc, err := c.parseService(groupType, method, info != nil && info.ArgIsRequire, func(format egoc.TimeFormatter) (string, *time.Location) {
				var (
					fmt string
					loc *time.Location
				)
				if info != nil {
					if info.TimeFormat != nil {
						fmt, loc = info.TimeFormat.Get(format)
					}
					if fmt != "" {
						return fmt, loc
					}
				}
				if groupDesc.TimeFormat != nil {
					fmt, loc = groupDesc.TimeFormat.Get(format)
				}
				if fmt != "" {
					return fmt, loc
				}
				return c.TimeFormat.Get(format)
			})
			if err != nil {
				// 同名 service 已经存在
				c.log.Warnf("func %s.%s is not a available service\n%s", groupType.Name(), method.Name, err.Error())
				continue
			}
			svc.Error = groupDesc.Error
			svc.Name = svcName

			if info != nil {
				svc.Desc = info.Description
				svc.ArgIsRequire = info.ArgIsRequire
				svc.Deprecated = info.Deprecated || groupDesc.Deprecated
				svc.ArgumentIsRequired = info.ArgIsRequire
				svc.IgnoreResponse = info.IgnoreResponse
				svc.Upload, svc.WebSocket = info.Upload, info.WebSocket
				if info.Error != "" {
					svc.Error = info.Error
				}
			}
			grp.Services = append(grp.Services, svc)
			c.services[svc.Name] = svc
		}
	}

	return nil
}

func (c *Core) GetGroups() []*function.GroupInfo {
	return c.groups
}

// Service 执行服务
// 返回参数 1 执行结果
// 返回参数 2 是否包含有定了 IgnoreResponse 是 true 的函数，包含一个即为 true
func (c *Core) Service(request data.Request, ctx *egoc.Context) (map[string]interface{}, bool, string, error) {
	var instances []*ServiceInstance
	// var injectValues = map[reflect.Type]*function.InjectValue{}
	var ignoreResponse = false
	var reqSet = map[string]struct{}{}
	var argCtx = &function.ArgContext{
		// InjectValueMap: map[reflect.Type]*function.InjectValue{},
		// GetTimeFormat:  c.getTimeFormat,
		ParseRequest: input.ParseRequest,
	}
	for _, req := range request {
		var svcName = req.Service
		if req.Alias != "" {
			svcName = req.Alias
		}
		if _, ok := reqSet[svcName]; ok {
			return nil, false, "", errs.NewStatusError(fmt.Errorf("api %s is duplicate", svcName), errs.StatusInvalidToken)
		}
		ctx.Service = req.Service
		argCtx.Reset()
		instance, ignore, err := c.createService(req, ctx, argCtx)
		if err != nil {
			return nil, false, "", err
		}
		if ignore {
			ignoreResponse = ignore
		}
		instances = append(instances, instance)
	}
	var response = map[string]interface{}{}
	for _, ins := range instances {
		res, err := ins.Execute()
		if err != nil {
			c.log.Errorf("service [%s] execute error: %s", ins.Service.Service, err.Error())
			// return nil, errs.NewStatusError(err, errs.StatusServiceNotFound)
			return nil, ignoreResponse, ins.Error, err
		}
		var svcName = ins.Service.Service
		if ins.Service.Alias != "" {
			svcName = ins.Service.Alias
		}
		response[svcName] = res
	}
	return response, ignoreResponse, "", nil
}

// func (c *Core) getTimeFormat(typ validator.TimeFormat) string {
// 	if typ == validator.TagDatetime {
// 		return c.tfDatetime
// 	}
// 	if typ == validator.TagDate {
// 		return c.tfDate
// 	}
// 	if typ == validator.TagTime {
// 		return c.tfTime
// 	}
// 	return ""
// }

func (c *Core) createService(request *data.ServiceRequest, ctx *egoc.Context, argCtx *function.ArgContext) (*ServiceInstance, bool, error) {
	svc := c.services[request.Service]
	if svc == nil {
		c.log.Errorf("service [%s] is not found", request.Service)
		return nil, false, errs.NewStatusError(fmt.Errorf("service [%s] is not found", request.Service),
			errs.StatusServiceNotFound)
	}
	var args []reflect.Value
	for _, arg := range svc.Arguments {
		argVal, err := arg.Value(request, ctx, argCtx)
		if err != nil {
			c.log.Errorf("service [%s] argument parse error: %s", request.Service, err.Error())
			if se, ok := err.(*errs.ErrorWithStatus); ok {
				return nil, false, se
			}
			return nil, false, errs.NewStatusError(fmt.Errorf("service [%s] argument parse error: %s", request.Service, err.Error()),
				errs.StatusIllegalArgument)
		}
		args = append(args, argVal)
	}
	return &ServiceInstance{
		method:  svc.Method,
		args:    args,
		Service: request,
		Error:   svc.Error,
	}, svc.IgnoreResponse, nil
}

func (c *Core) findGroup(name string, deprecated bool) *function.GroupInfo {
	var g *function.GroupInfo
	for _, grp := range c.groups {
		if grp.Name == name {
			grp.Deprecated = grp.Deprecated && deprecated
			return grp
		}
	}
	g = &function.GroupInfo{Name: name, Deprecated: deprecated}
	c.groups = append(c.groups, g)
	return g
}

// tryToParseFuncInfo 尝试解析函数描述信息
func tryToParseFuncInfo(loader interface{}, group egoc.Group, loaderType reflect.Type, method reflect.Method) (*egoc.FuncInfo, string) {
	// m, exists := loaderType.MethodByName(funcName + "Info")
	// if !exists {
	// 	return nil
	// }

	mType := method.Type
	if mType.NumIn() != 1 || mType.NumOut() != 1 {
		return nil, ""
	}

	var isPtr = false
	outType := mType.Out(0)
	if outType.Kind() == reflect.Ptr {
		outType = outType.Elem()
		isPtr = true
	}
	if !types.IsFuncInfoType(outType) {
		return nil, ""
	}

	svcName := fmt.Sprintf("%s.%s", group.Name, method.Name)
	out := method.Func.Call([]reflect.Value{reflect.ValueOf(loader)})
	if isPtr {
		return out[0].Interface().(*egoc.FuncInfo), svcName
	}
	var info = out[0].Interface().(egoc.FuncInfo)
	return &info, svcName
}
